#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <unistd.h>

#include "imbus_internal.h"
#include "im_memory_chunk.h"
#include "im_stream.h"
#include "im_text_stream.h"

#define MAX_ALLOC_BLOCK_SIZE     65536

struct _IMTextStream
{
    IMStream parent_object;

    IMMemoryChunk   *_chunk;

    IMSize           _write_pos;
    IMSize           _read_pos;

    IMType           _cur_type;
    IMSize           _cur_size;
    IMSize           _cur_type_len;
    IMSize           _cur_size_len;
};

struct _IMTextStreamClass
{
    IMStreamClass    parent_class;
};

static const IMObjectClass *__im_text_stream_parent_class = 0;

#define HEX_TO_CHAR(hex) (((hex) <= 9) ? ('0' + (hex)) : ('a' + (hex) - 10))
#define CHAR_TO_HEX(ch)  (((ch) >= '0' && (ch) <= '9') ? ((ch) - '0') : (ch + 10 - 'a'))

static void
__free_unused_space (const IMTextStream *stream)
{
    if (!stream->_write_pos)
        im_memory_chunk_clear (stream->_chunk);
    else if (im_memory_chunk_get_size (stream->_chunk) > stream->_write_pos)
        im_memory_chunk_reduce_size (stream->_chunk, stream->_write_pos);
}

/* return how many bytes encoded and how many chars used.  */
static void
__encode_bytes_to_text (const IMChar *bytes, IMSize *nbytes, IMChar *text, IMSize *nchars)
{
    IMChar *in = (IMChar *) bytes;
    IMChar *in_end = (IMChar *) bytes + *nbytes;
    IMChar *out = (IMChar *) text;
    IMChar *out_end = (IMChar *) text + *nchars;

    while (in < in_end && out < out_end) {
        if (iscntrl(*in) || *in == '\\' || *in == ':' || *in == '\n' || *in == 0) {
            if (out_end - out < 4) break;
            *out++ = '\\';
            *out++ = 'x';
            *out++ = HEX_TO_CHAR((*in) >> 4);
            *out++ = HEX_TO_CHAR((*in) & 0xF);
            ++in;
        } else {
            *out++ = *in++;
        }
    }

    if (out < out_end)
        *out = 0;

    *nbytes = in - bytes;
    *nchars = out - text;
}

/* return how many chars decoded and how many bytes got */
static void
__decode_bytes_from_text (const IMChar *text, IMSize *nchars, IMChar *bytes, IMSize *nbytes)
{
    IMChar *in = (IMChar *) text;
    IMChar *in_end = (IMChar *) text + *nchars;
    IMChar *out = (IMChar *) bytes;
    IMChar *out_end = (IMChar *) bytes + *nbytes;
    IMChar lo, hi;

    while (in < in_end && out < out_end) {
        if (*in == '\\') {
            if (in_end - in < 4 || *(in+1) != 'x') break;
            in += 2;
            hi = CHAR_TO_HEX(*in);
            ++in;
            lo = CHAR_TO_HEX(*in);
            ++in;
            *out++ = ((hi << 4) | (lo & 0xF));
        } else {
            *out++ = *in++;
        }
    }

    *nbytes = out - bytes;
    *nchars = in - text;
}

static inline IMBool
__ensure_free_space (IMTextStream *stream, IMSize free_size)
{
    IMSize old_size = im_memory_chunk_get_size (stream->_chunk);
    if ( old_size < stream->_write_pos + free_size) {
        IMSize new_size = stream->_write_pos + free_size;

        if (old_size < MAX_ALLOC_BLOCK_SIZE && new_size - old_size < old_size * 2)
            new_size = old_size * 2;
        else if (new_size - old_size < MAX_ALLOC_BLOCK_SIZE)
            new_size = old_size + MAX_ALLOC_BLOCK_SIZE;

        return im_memory_chunk_ensure_size (stream->_chunk, new_size);
    }
    return TRUE;
}
static inline void
__clear_cur_info (IMTextStream *stream)
{
    stream->_cur_type       = IM_TYPE_INVALID;
    stream->_cur_size       = 0;
    stream->_cur_type_len   = 0;
    stream->_cur_size_len   = 0;
}

#define ENSURE_FREE_SPACE(tsp,size)                 \
    do {                                            \
        if (!__ensure_free_space ((tsp), (size)))   \
            return FALSE;                           \
    } while (0)

static IMBool
__im_text_stream_put_void (IMStream *stream)
{
    const IMChar *name = im_type_get_name (IM_TYPE_VOID);
    IMTextStream *tsp = IM_TEXT_STREAM (stream);
    IMChar       *ptr;
    IMSize        len;
    _im_assert (tsp);
    len = strlen (name);
    ENSURE_FREE_SPACE(tsp, len + 1);
    ptr = (IMChar *) im_memory_chunk_get_ptr (tsp->_chunk);
    ptr += tsp->_write_pos;
    memcpy (ptr, name, len);
    ptr += len;
    *ptr = '\n';
    tsp->_write_pos += (len + 1);
    return TRUE;
}

#define PUT_FUNDAMENTAL_VALUE_TYPE(stream,type,format,value)        \
    do  {                                                           \
        const IMChar *name = im_type_get_name (type);               \
        IMTextStream *tsp = IM_TEXT_STREAM (stream);                \
        IMChar       *ptr;                                          \
        IMSize        len;                                          \
        IMChar        buf [128];                                    \
        _im_assert (tsp);                                           \
        len = snprintf (buf, 128, format, name, (value));           \
        ENSURE_FREE_SPACE(tsp, len + 1);                            \
        ptr = (IMChar *) im_memory_chunk_get_ptr (tsp->_chunk);     \
        ptr += tsp->_write_pos;                                     \
        memcpy (ptr, buf, len);                                     \
        ptr += len;                                                 \
        *ptr = '\n';                                                \
        tsp->_write_pos += (len + 1);                               \
        return TRUE;                                                \
    } while (0)

static IMBool
__im_text_stream_put_char (IMStream *stream, IMChar v_char)
{
    PUT_FUNDAMENTAL_VALUE_TYPE(stream,IM_TYPE_CHAR,"%s:%d",v_char);
}
static IMBool
__im_text_stream_put_uchar (IMStream *stream, IMUChar v_uchar)
{
    PUT_FUNDAMENTAL_VALUE_TYPE(stream,IM_TYPE_UCHAR,"%s:%u",v_uchar);
}
static IMBool
__im_text_stream_put_bool (IMStream *stream, IMBool v_bool)
{
    PUT_FUNDAMENTAL_VALUE_TYPE(stream,IM_TYPE_BOOL,"%s:%c",(v_bool ? '1' : '0'));
}
static IMBool
__im_text_stream_put_int16 (IMStream *stream, IMInt16 v_int16)
{
    PUT_FUNDAMENTAL_VALUE_TYPE(stream,IM_TYPE_INT16,"%s:%d",v_int16);
}
static IMBool
__im_text_stream_put_uint16 (IMStream *stream, IMUInt16 v_uint16)
{
    PUT_FUNDAMENTAL_VALUE_TYPE(stream,IM_TYPE_UINT16,"%s:%u",v_uint16);
}
static IMBool
__im_text_stream_put_int32 (IMStream *stream, IMInt32 v_int32)
{
    PUT_FUNDAMENTAL_VALUE_TYPE(stream,IM_TYPE_INT32,"%s:%d",v_int32);
}
static IMBool
__im_text_stream_put_uint32 (IMStream *stream, IMUInt32 v_uint32)
{
    PUT_FUNDAMENTAL_VALUE_TYPE(stream,IM_TYPE_UINT32,"%s:%u",v_uint32);
}
static IMBool
__im_text_stream_put_int64 (IMStream *stream, IMInt64 v_int64)
{
    PUT_FUNDAMENTAL_VALUE_TYPE(stream,IM_TYPE_INT64,"%s:%" IM_IMINT64_FORMAT,v_int64);
}
static IMBool
__im_text_stream_put_uint64 (IMStream *stream, IMUInt64 v_uint64)
{
    PUT_FUNDAMENTAL_VALUE_TYPE(stream,IM_TYPE_UINT64,"%s:%" IM_IMUINT64_FORMAT,v_uint64);
}
static IMBool
__im_text_stream_put_double (IMStream *stream, IMDouble v_double)
{
    PUT_FUNDAMENTAL_VALUE_TYPE(stream,IM_TYPE_DOUBLE,"%s:%.8f",v_double);
}
static IMBool
__im_text_stream_put_c_string (IMStream *stream, const IMChar *v_string, IMSize v_length)
{
    const IMChar *name = im_type_get_name (IM_TYPE_C_STRING);
    IMTextStream *tsp = IM_TEXT_STREAM (stream);
    IMChar       *ptr;
    IMSize        nlen;
    IMSize        slen;
    IMSize        olen;
    IMChar        buf [128];
    _im_assert (tsp);
    slen = IM_MIN (strlen (v_string), v_length);
    nlen = snprintf (buf, 128, "%s:%lu:", name, slen);
    ENSURE_FREE_SPACE(tsp, nlen + slen * 4 + 1);
    ptr = (IMChar *) im_memory_chunk_get_ptr (tsp->_chunk);
    ptr += tsp->_write_pos;
    memcpy (ptr, buf, nlen);
    ptr += nlen;
    olen = slen * 4;
    __encode_bytes_to_text (v_string, &slen, ptr, &olen);
    ptr += olen;
    *ptr = '\n';
    tsp->_write_pos += (nlen + olen + 1);
    return TRUE;
}

#define PUT_FUNDAMENTAL_VALUE_ARRAY_TYPE(stream,type,format,array,num)  \
    do  {                                                               \
        const IMChar *name = im_type_get_name (type);                   \
        IMTextStream *tsp = IM_TEXT_STREAM (stream);                    \
        IMChar       *ptr;                                              \
        IMSize        len;                                              \
        IMSize        i;                                                \
        IMChar        buf [128];                                        \
        _im_assert (tsp);                                               \
        len = snprintf (buf, 128, "%s:%u:", name, num);                 \
        ENSURE_FREE_SPACE(tsp, len + 1);                                \
        ptr = (IMChar *) im_memory_chunk_get_ptr (tsp->_chunk);         \
        ptr += tsp->_write_pos;                                         \
        memcpy (ptr, buf, len);                                         \
        tsp->_write_pos += len;                                         \
        for (i = 0; i < num; ++i) {                                     \
            len = snprintf (buf, 128, format, (array)[i]);              \
            ENSURE_FREE_SPACE(tsp, len + 1);                            \
            ptr = (IMChar *) im_memory_chunk_get_ptr (tsp->_chunk);     \
            ptr += tsp->_write_pos;                                     \
            memcpy (ptr, buf, len);                                     \
            ptr += len;                                                 \
            if (i < num - 1) *ptr = ',';                                \
            else *ptr = '\n';                                           \
            tsp->_write_pos += (len + 1);                               \
        }                                                               \
        return TRUE;                                                    \
    } while (0)

static IMBool
__im_text_stream_put_byte_array (IMStream *stream, const IMPointer v_array, IMSize v_size)
{
    const IMChar *name = im_type_get_name (IM_TYPE_BYTE_ARRAY);
    IMTextStream *tsp = IM_TEXT_STREAM (stream);
    IMChar       *ptr;
    IMSize        nlen;
    IMSize        olen;
    IMChar        buf [128];
    _im_assert (tsp);
    nlen = snprintf (buf, 128, "%s:%lu:", name, v_size);
    ENSURE_FREE_SPACE(tsp, nlen + v_size * 4 + 1);
    ptr = (IMChar *) im_memory_chunk_get_ptr (tsp->_chunk);
    ptr += tsp->_write_pos;
    memcpy (ptr, buf, nlen);
    ptr += nlen;
    olen = v_size * 4;
    __encode_bytes_to_text (v_array, &v_size, ptr, &olen);
    ptr += olen;
    *ptr = '\n';
    tsp->_write_pos += (nlen + olen + 1);
    return TRUE;
}
static IMBool
__im_text_stream_put_bool_array (IMStream *stream, const IMBool *v_array, IMSize v_size)
{
    PUT_FUNDAMENTAL_VALUE_ARRAY_TYPE(stream,IM_TYPE_BOOL_ARRAY,"%d",v_array,v_size);
}
static IMBool
__im_text_stream_put_int16_array (IMStream *stream, const IMInt16 *v_array, IMSize v_size)
{
    PUT_FUNDAMENTAL_VALUE_ARRAY_TYPE(stream,IM_TYPE_INT16_ARRAY,"%d",v_array,v_size);
}
static IMBool
__im_text_stream_put_uint16_array (IMStream *stream, const IMUInt16 *v_array, IMSize v_size)
{
    PUT_FUNDAMENTAL_VALUE_ARRAY_TYPE(stream,IM_TYPE_UINT16_ARRAY,"%u",v_array,v_size);
}
static IMBool
__im_text_stream_put_int32_array (IMStream *stream, const IMInt32 *v_array, IMSize v_size)
{
    PUT_FUNDAMENTAL_VALUE_ARRAY_TYPE(stream,IM_TYPE_INT32_ARRAY,"%d",v_array,v_size);
}
static IMBool
__im_text_stream_put_uint32_array (IMStream *stream, const IMUInt32 *v_array, IMSize v_size)
{
    PUT_FUNDAMENTAL_VALUE_ARRAY_TYPE(stream,IM_TYPE_UINT32_ARRAY,"%u",v_array,v_size);
}
static IMBool
__im_text_stream_put_int64_array (IMStream *stream, const IMInt64 *v_array, IMSize v_size)
{
    PUT_FUNDAMENTAL_VALUE_ARRAY_TYPE(stream,IM_TYPE_INT64_ARRAY,"%" IM_IMINT64_FORMAT,v_array,v_size);
}
static IMBool
__im_text_stream_put_uint64_array (IMStream *stream, const IMUInt64 *v_array, IMSize v_size)
{
    PUT_FUNDAMENTAL_VALUE_ARRAY_TYPE(stream,IM_TYPE_UINT64_ARRAY,"%" IM_IMUINT64_FORMAT,v_array,v_size);
}
static IMBool
__im_text_stream_put_double_array (IMStream *stream, const IMDouble *v_array, IMSize v_size)
{
    PUT_FUNDAMENTAL_VALUE_ARRAY_TYPE(stream,IM_TYPE_DOUBLE_ARRAY,"%0.8f",v_array,v_size);
}
static IMBool
__im_text_stream_put_object (IMStream *stream, IMConstPointer v_object)
{
    const IMChar    *name;
    IMTextStream    *tsp = IM_TEXT_STREAM (stream);
    const IMObject  *obj = IM_CONST_OBJECT (v_object);
    IMChar          *ptr;
    IMChar           buf [128];
    IMSize           len;
    _im_assert (tsp && obj);
    name = im_type_get_name (im_object_get_type (obj));
    len = snprintf (buf, 128, "%s:BEGIN:", name);
    ENSURE_FREE_SPACE(tsp, len + 1);
    ptr = (IMChar *) im_memory_chunk_get_ptr (tsp->_chunk);
    ptr += tsp->_write_pos;
    memcpy (ptr, buf, len);
    ptr += len;
    *ptr = '\n';
    tsp->_write_pos += (len + 1);
    if (!im_object_serialize (obj, stream)) return FALSE;
    len = snprintf (buf, 128, "%s:END:", name);
    ENSURE_FREE_SPACE(tsp, len + 1);
    ptr = (IMChar *) im_memory_chunk_get_ptr (tsp->_chunk);
    ptr += tsp->_write_pos;
    memcpy (ptr, buf, len);
    ptr += len;
    *ptr = '\n';
    tsp->_write_pos += (len + 1);
    return TRUE;
}
static void
__im_text_stream_clear (IMStream *stream)
{
    IMTextStream *tsp = IM_TEXT_STREAM (stream);
    _im_assert (tsp);
    tsp->_write_pos = 0;
    tsp->_read_pos = 0;
    __clear_cur_info (tsp);

    /* Only clear the memory chunk if it's too large. */
    if (im_memory_chunk_get_size (tsp->_chunk) > MAX_ALLOC_BLOCK_SIZE)
        im_memory_chunk_clear (tsp->_chunk);
}

static IMType
__im_text_stream_get_data_type (IMStream *stream)
{
    IMTextStream *tsp = IM_TEXT_STREAM (stream);
    const IMChar *ptr;
    const IMChar *ptr_end;
    const IMChar *end;
    IMChar        buf [128];
    IMSize        len;
    _im_assert (tsp);

    if (tsp->_cur_type != IM_TYPE_INVALID)
        return tsp->_cur_type;

    if (tsp->_read_pos >= tsp->_write_pos)
        return IM_TYPE_INVALID;

    ptr  = im_memory_chunk_get_const_ptr (tsp->_chunk);
    end  = ptr + tsp->_write_pos;
    ptr += tsp->_read_pos;
    ptr_end = ptr;

    while (*ptr_end != ':' && *ptr_end != '\n' && ptr_end < end) ++ptr_end;

    len = IM_MIN (ptr_end - ptr, 127);
    memcpy (buf, ptr, len);
    buf [len] = 0;

    tsp->_cur_type = im_type_from_name (buf);
    tsp->_cur_type_len = ptr_end - ptr;

    return tsp->_cur_type;
}

static IMSize
__im_text_stream_get_data_size (IMStream *stream)
{
    IMType        type = __im_text_stream_get_data_type (stream);
    IMTextStream *tsp  = IM_TEXT_STREAM (stream);
    const IMChar *ptr;
    const IMChar *ptr_end;
    const IMChar *end;
    IMChar        buf [128];
    IMSize        len;
    _im_assert (tsp);

    if (type == IM_TYPE_INVALID || type == IM_TYPE_VOID)
        return 0;

    if (tsp->_cur_size)
        return tsp->_cur_size;

    if (IM_TYPE_IS_OBJECT (type) || (type >= IM_TYPE_VOID && type <= IM_TYPE_DOUBLE)) {
        tsp->_cur_size = im_type_get_instance_size (type);
        return tsp->_cur_size;
    }

    ptr  = im_memory_chunk_get_const_ptr (tsp->_chunk);
    end  = ptr + tsp->_write_pos;
    ptr += tsp->_read_pos;
    ptr += tsp->_cur_type_len;

    if (*ptr++ != ':')
        return 0;

    ptr_end = ptr;

    while (*ptr_end != ':' && *ptr_end != '\n' && ptr_end < end) ++ptr_end;

    len = IM_MIN (ptr_end - ptr, 127);
    memcpy (buf, ptr, len);
    buf [len] = 0;

    tsp->_cur_size = strtol (buf, 0, 10);
    tsp->_cur_size_len = len;

    return tsp->_cur_size;
}
static IMBool
__im_text_stream_get_void (IMStream *stream)
{
    IMType        type = __im_text_stream_get_data_type (stream);
    IMTextStream *tsp  = IM_TEXT_STREAM (stream);
    const IMChar *ptr;
    const IMChar *begin;
    const IMChar *end;
    _im_assert (tsp);

    if (type == IM_TYPE_VOID) {
        begin = im_memory_chunk_get_const_ptr (tsp->_chunk);
        end   = begin + tsp->_write_pos;
        ptr   = begin + tsp->_read_pos;

        while (*ptr != '\n' && ptr < end) ++ptr;

        if (ptr < end) ++ptr;

        tsp->_read_pos = ptr - begin;

        __clear_cur_info (tsp);
        return TRUE;
    }

    return FALSE;
}

#define GET_FUNDAMENTAL_VALUE_TYPE(stream,dtype,cast_type,convfunc,val)     \
    do {                                                                    \
        IMType        type = __im_text_stream_get_data_type (stream);       \
        IMTextStream *tsp  = IM_TEXT_STREAM (stream);                       \
        const IMChar *ptr;                                                  \
        const IMChar *begin;                                                \
        const IMChar *end;                                                  \
        _im_assert (tsp);                                                   \
                                                                            \
        if (type == (dtype)) {                                              \
            begin = im_memory_chunk_get_const_ptr (tsp->_chunk);            \
            end   = begin + tsp->_write_pos;                                \
            ptr   = begin + tsp->_read_pos;                                 \
            ptr  += tsp->_cur_type_len;                                     \
            if (*ptr++ != ':') return FALSE;                                \
                                                                            \
            if (val)                                                        \
                *val = (cast_type) convfunc;                                \
                                                                            \
            while (*ptr != '\n' && ptr < end) ++ptr;                        \
            if (ptr < end) ++ptr;                                           \
            tsp->_read_pos = ptr - begin;                                   \
                                                                            \
            __clear_cur_info (tsp);                                         \
            return TRUE;                                                    \
        }                                                                   \
    } while (0)

static IMBool
__im_text_stream_get_char (IMStream *stream, IMChar *v_char)
{
    GET_FUNDAMENTAL_VALUE_TYPE(stream,IM_TYPE_CHAR,IMChar,strtol(ptr,0,10),v_char);
    return FALSE;
}
static IMBool
__im_text_stream_get_uchar (IMStream *stream, IMUChar *v_uchar)
{
    GET_FUNDAMENTAL_VALUE_TYPE(stream,IM_TYPE_UCHAR,IMUChar,strtoul(ptr,0,10),v_uchar);
    return FALSE;
}
static IMBool
__im_text_stream_get_bool (IMStream *stream, IMBool *v_bool)
{
    GET_FUNDAMENTAL_VALUE_TYPE(stream,IM_TYPE_BOOL,IMBool,strtol(ptr,0,10),v_bool);
    return FALSE;
}
static IMBool
__im_text_stream_get_int16 (IMStream *stream, IMInt16 *v_int16)
{
    GET_FUNDAMENTAL_VALUE_TYPE(stream,IM_TYPE_INT16,IMInt16,strtol(ptr,0,10),v_int16);
    return FALSE;
}
static IMBool
__im_text_stream_get_uint16 (IMStream *stream, IMUInt16 *v_uint16)
{
    GET_FUNDAMENTAL_VALUE_TYPE(stream,IM_TYPE_UINT16,IMUInt16,strtoul(ptr,0,10),v_uint16);
    return FALSE;
}
static IMBool
__im_text_stream_get_int32 (IMStream *stream, IMInt32 *v_int32)
{
    GET_FUNDAMENTAL_VALUE_TYPE(stream,IM_TYPE_INT32,IMInt32,strtol(ptr,0,10),v_int32);
    return FALSE;
}
static IMBool
__im_text_stream_get_uint32 (IMStream *stream, IMUInt32 *v_uint32)
{
    GET_FUNDAMENTAL_VALUE_TYPE(stream,IM_TYPE_UINT32,IMUInt32,strtoul(ptr,0,10),v_uint32);
    return FALSE;
}
static IMBool
__im_text_stream_get_int64 (IMStream *stream, IMInt64 *v_int64)
{
    GET_FUNDAMENTAL_VALUE_TYPE(stream,IM_TYPE_INT64,IMInt64,strtoll(ptr,0,10),v_int64);
    return FALSE;
}
static IMBool
__im_text_stream_get_uint64 (IMStream *stream, IMUInt64 *v_uint64)
{
    GET_FUNDAMENTAL_VALUE_TYPE(stream,IM_TYPE_UINT64,IMUInt64,strtoull(ptr,0,10),v_uint64);
    return FALSE;
}
static IMBool
__im_text_stream_get_double (IMStream *stream, IMDouble *v_double)
{
    GET_FUNDAMENTAL_VALUE_TYPE(stream,IM_TYPE_DOUBLE,IMDouble,strtod(ptr,0),v_double);
    return FALSE;
}
static IMBool
__im_text_stream_get_c_string (IMStream *stream, IMChar *v_string, IMSize v_length)
{
    IMType        type = __im_text_stream_get_data_type (stream);
    IMSize        len  = __im_text_stream_get_data_size (stream);
    IMTextStream *tsp  = IM_TEXT_STREAM (stream);
    const IMChar *ptr;
    const IMChar *ptr_end;
    const IMChar *begin;
    const IMChar *end;
    IMSize        nchars;
    _im_assert (tsp);

    if (type == IM_TYPE_C_STRING && len) {
        begin = im_memory_chunk_get_const_ptr (tsp->_chunk);
        end   = begin + tsp->_write_pos;
        ptr   = begin + tsp->_read_pos;
        ptr  += tsp->_cur_type_len;
        if (*ptr++ != ':') return FALSE;
        ptr  += tsp->_cur_size_len;
        if (*ptr++ != ':') return FALSE;
        ptr_end = ptr;

        while (*ptr_end != '\n' && ptr_end < end) ++ptr_end;

        nchars = ptr_end - ptr;

        len = IM_MIN (len, v_length);

        if (v_string && len) {
            __decode_bytes_from_text (ptr, &nchars, v_string, &len);
            if (len < v_length) v_string [len] = 0;
        }

        if (ptr_end < end) ++ptr_end;

        tsp->_read_pos = ptr_end - begin;

        __clear_cur_info (tsp);
        return TRUE;
    }

    return FALSE;
}
static IMBool
__im_text_stream_get_byte_array (IMStream *stream, IMPointer v_array, IMSize v_size)
{
    IMType        type = __im_text_stream_get_data_type (stream);
    IMSize        len  = __im_text_stream_get_data_size (stream);
    IMTextStream *tsp  = IM_TEXT_STREAM (stream);
    const IMChar *ptr;
    const IMChar *ptr_end;
    const IMChar *begin;
    const IMChar *end;
    IMSize        nchars;
    _im_assert (tsp);

    if (type == IM_TYPE_BYTE_ARRAY && len) {
        begin = im_memory_chunk_get_const_ptr (tsp->_chunk);
        end   = begin + tsp->_write_pos;
        ptr   = begin + tsp->_read_pos;
        ptr  += tsp->_cur_type_len;
        if (*ptr++ != ':') return FALSE;
        ptr  += tsp->_cur_size_len;
        if (*ptr++ != ':') return FALSE;
        ptr_end = ptr;

        while (*ptr_end != '\n' && ptr_end < end) ++ptr_end;

        nchars = ptr_end - ptr;

        len = IM_MIN (len, v_size);

        if (v_array && len)
            __decode_bytes_from_text (ptr, &nchars, (IMChar *) v_array, &len);

        if (ptr_end < end) ++ptr_end;

        tsp->_read_pos = ptr_end - begin;

        __clear_cur_info (tsp);
        return TRUE;
    }

    return FALSE;
}

#define GET_FUNDAMENTAL_VALUE_ARRAY_TYPE(stream,dtype,cast_type,convfunc,array,nmemb)   \
    do {                                                                                \
        IMType        type = __im_text_stream_get_data_type (stream);                   \
        IMSize        size = __im_text_stream_get_data_size (stream);                   \
        IMTextStream *tsp  = IM_TEXT_STREAM (stream);                                   \
        const IMChar *ptr;                                                              \
        const IMChar *begin;                                                            \
        const IMChar *end;                                                              \
        _im_assert (tsp);                                                               \
                                                                                        \
        if (type == (dtype)) {                                                          \
            begin = im_memory_chunk_get_const_ptr (tsp->_chunk);                        \
            end   = begin + tsp->_write_pos;                                            \
            ptr   = begin + tsp->_read_pos;                                             \
            ptr  += tsp->_cur_type_len;                                                 \
            if (*ptr++ != ':') return FALSE;                                            \
            ptr  += tsp->_cur_size_len;                                                 \
            if (*ptr++ != ':') return FALSE;                                            \
                                                                                        \
            size = IM_MIN (size, nmemb);                                                \
            if (array) {                                                                \
                for (nmemb = 0; nmemb < size; ++nmemb) {                                \
                    (array)[nmemb] = (cast_type) convfunc;                              \
                    while (*ptr != ',' && *ptr != '\n' && ptr < end)                    \
                        ++ptr;                                                          \
                    if (*ptr != ',') break;                                             \
                    ++ptr;                                                              \
                }                                                                       \
            }                                                                           \
                                                                                        \
            while (*ptr != '\n' && ptr < end) ++ptr;                                    \
                                                                                        \
            if (ptr < end) ++ptr;                                                       \
                                                                                        \
            tsp->_read_pos = ptr - begin;                                               \
                                                                                        \
            __clear_cur_info (tsp);                                                     \
            return TRUE;                                                                \
        }                                                                               \
    } while (0)

static IMBool
__im_text_stream_get_bool_array (IMStream *stream, IMBool *v_array, IMSize v_size)
{
    GET_FUNDAMENTAL_VALUE_ARRAY_TYPE(stream,IM_TYPE_BOOL_ARRAY,IMBool,strtol(ptr,0,10),v_array,v_size);
    return FALSE;
}
static IMBool
__im_text_stream_get_int16_array (IMStream *stream, IMInt16 *v_array, IMSize v_size)
{
    GET_FUNDAMENTAL_VALUE_ARRAY_TYPE(stream,IM_TYPE_INT16_ARRAY,IMInt16,strtol(ptr,0,10),v_array,v_size);
    return FALSE;
}
static IMBool
__im_text_stream_get_uint16_array (IMStream *stream, IMUInt16 *v_array, IMSize v_size)
{
    GET_FUNDAMENTAL_VALUE_ARRAY_TYPE(stream,IM_TYPE_UINT16_ARRAY,IMUInt16,strtoul(ptr,0,10),v_array,v_size);
    return FALSE;
}
static IMBool
__im_text_stream_get_int32_array (IMStream *stream, IMInt32 *v_array, IMSize v_size)
{
    GET_FUNDAMENTAL_VALUE_ARRAY_TYPE(stream,IM_TYPE_INT32_ARRAY,IMInt32,strtol(ptr,0,10),v_array,v_size);
    return FALSE;
}
static IMBool
__im_text_stream_get_uint32_array (IMStream *stream, IMUInt32 *v_array, IMSize v_size)
{
    GET_FUNDAMENTAL_VALUE_ARRAY_TYPE(stream,IM_TYPE_UINT32_ARRAY,IMUInt32,strtoul(ptr,0,10),v_array,v_size);
    return FALSE;
}
static IMBool
__im_text_stream_get_int64_array (IMStream *stream, IMInt64 *v_array, IMSize v_size)
{
    GET_FUNDAMENTAL_VALUE_ARRAY_TYPE(stream,IM_TYPE_INT64_ARRAY,IMInt64,strtoll(ptr,0,10),v_array,v_size);
    return FALSE;
}
static IMBool
__im_text_stream_get_uint64_array (IMStream *stream, IMUInt64 *v_array, IMSize v_size)
{
    GET_FUNDAMENTAL_VALUE_ARRAY_TYPE(stream,IM_TYPE_UINT64_ARRAY,IMUInt64,strtoull(ptr,0,10),v_array,v_size);
    return FALSE;
}
static IMBool
__im_text_stream_get_double_array (IMStream *stream, IMDouble *v_array, IMSize v_size)
{
    GET_FUNDAMENTAL_VALUE_ARRAY_TYPE(stream,IM_TYPE_DOUBLE_ARRAY,IMDouble,strtod(ptr,0),v_array,v_size);
    return FALSE;
}
static IMBool
__im_text_stream_get_object (IMStream *stream, IMPointer v_object)
{
    IMType        type = __im_text_stream_get_data_type (stream);
    IMTextStream *tsp  = IM_TEXT_STREAM (stream);
    IMType        otype = im_object_get_type (v_object);
    IMSize        old_read_pos;
    IMSize        type_len;
    const IMChar *ptr;
    const IMChar *begin;
    const IMChar *end;
    _im_assert (tsp);

    if (type == otype && IM_TYPE_IS_OBJECT (type)) {
        type_len = tsp->_cur_type_len;
        begin    = im_memory_chunk_get_const_ptr (tsp->_chunk);
        end      = begin + tsp->_write_pos;
        ptr      = begin + tsp->_read_pos;
        ptr     += type_len;
        if (*ptr++ != ':') return FALSE;
        if (strncmp (ptr, "BEGIN", 5)) return FALSE; 

        while (*ptr != '\n' && ptr < end) ++ptr;

        if (ptr < end) ++ptr;

        old_read_pos = tsp->_read_pos;

        tsp->_read_pos = ptr - begin;

        __clear_cur_info (tsp);

        if (!im_object_deserialize (v_object, stream)) {
            tsp->_read_pos = old_read_pos;
            return FALSE;
        }
        ptr = begin + tsp->_read_pos;
        if (strncmp (ptr, begin + old_read_pos, type_len)) {
            tsp->_read_pos = old_read_pos;
            return FALSE;
        }
        ptr += type_len;
        if (*ptr++ != ':') {
            tsp->_read_pos = old_read_pos;
            return FALSE;
        }
        if (strncmp (ptr, "END", 3)) {
            tsp->_read_pos = old_read_pos;
            return FALSE;
        }

        while (*ptr != '\n' && ptr < end) ++ptr;
        if (ptr < end) ++ptr;
        tsp->_read_pos = ptr - begin;

        __clear_cur_info (tsp);
        return TRUE;
    }

    return FALSE;
}
static IMBool
__im_text_stream_skip_data (IMStream *stream)
{
    IMType        type = __im_text_stream_get_data_type (stream);
    IMTextStream *tsp  = IM_TEXT_STREAM (stream);
    const IMChar *ptr;
    const IMChar *begin;
    const IMChar *end;

    _im_assert (tsp);

    begin   = im_memory_chunk_get_const_ptr (tsp->_chunk);
    end     = begin + tsp->_write_pos;
    ptr     = begin + tsp->_read_pos;

    if (im_type_is_fundamental (type)) {
        while (*ptr != '\n' && ptr < end) ++ptr;
        if (ptr < end) ++ptr;
        tsp->_read_pos = ptr - begin;
        __clear_cur_info (tsp);
        return TRUE;
    } else if (IM_TYPE_IS_OBJECT (type)) {
        IMChar        begin_str [128];
        IMChar        end_str [128];
        IMSize        begin_len;
        IMSize        end_len;

        IMInt32       count;

        strncpy (begin_str, ptr, tsp->_cur_type_len);
        begin_str [tsp->_cur_type_len] = ':';
        strncpy (begin_str + tsp->_cur_type_len + 1, "BEGIN:", 6);
        begin_len = tsp->_cur_type_len + 7;
        begin_str [begin_len] = 0;

        strncpy (end_str, ptr, tsp->_cur_type_len);
        end_str [tsp->_cur_type_len] = ':';
        strncpy (end_str + tsp->_cur_type_len + 1, "END:", 4);
        end_len = tsp->_cur_type_len + 5;
        end_str [end_len] = 0;

        count = 1;

        while (*ptr != '\n' && ptr < end) ++ptr;
        if (ptr < end) ++ptr;

        while (count) {
            if (!strncmp (ptr, begin_str, begin_len))
                ++count;
            else if (!strncmp (ptr, end_str, end_len))
                --count;

            while (*ptr != '\n' && ptr < end) ++ptr;
            if (ptr < end) ++ptr;
            else break;
        }
        tsp->_read_pos = ptr - begin;
        __clear_cur_info (tsp);
        return TRUE;
    }

    return FALSE;
}
static IMBool
__im_text_stream_rewind (IMStream *stream)
{
    IMTextStream *p = IM_TEXT_STREAM (stream);

    _im_assert (p);

    p->_read_pos = 0;
    __clear_cur_info (p);
    
    return TRUE;
}

static IMBool
__im_text_stream_read (IMStream *stream, IMIOChannel *channel,
                       IMInt *timeout, IMIOError *error)
{
    IMChar buf [10];
    IMSize bytes;
    IMInt def_timeout = -1;
    IMTextStream *p = IM_TEXT_STREAM (stream);
    _im_assert (p);

    if (!timeout) timeout = &def_timeout;

    bytes = 9;
    if (!im_io_channel_read_with_timeout (channel, buf, bytes, &bytes,
                                          timeout, error) ||
        bytes != 9 || buf [8] != '\n') {
        if (error && *error == IM_IO_ERROR_NONE)
            *error = IM_IO_ERROR_FAILED;
        return FALSE;
    }

    buf [8] = 0;

    bytes = strtol (buf, 0, 16);

    if (bytes == 0 || bytes == (IMSize) (-1)) {
        return FALSE;
    }

    if (im_memory_chunk_load (p->_chunk, channel, bytes, timeout, error)) {
        p->_write_pos = im_memory_chunk_get_size (p->_chunk);
        p->_read_pos       = 0;
        __clear_cur_info (p);
        return TRUE;
    }

    return FALSE;
}

static IMBool
__im_text_stream_write (IMStream *stream, IMIOChannel *channel,
                        IMInt *timeout, IMIOError *error)
{
    IMChar buf [10];
    IMInt def_timeout = -1;
    IMSize bytes;
    IMTextStream *p = IM_TEXT_STREAM (stream);
    _im_assert (p);

    if (!timeout) timeout = &def_timeout;

    snprintf (buf, 10, "%08X\n", p->_write_pos);
    bytes = 9;

    if (!im_io_channel_write_with_timeout (channel, buf, bytes, &bytes,
                                           timeout, error) ||
        bytes != 9) {
        if (error && *error == IM_IO_ERROR_NONE)
            *error = IM_IO_ERROR_FAILED;
        return FALSE;
    }

    return im_memory_chunk_save (p->_chunk, channel, p->_write_pos,
                                 timeout, error);
}

static IMSize
__im_text_stream_size (IMStream *stream)
{
    IMTextStream *p = IM_TEXT_STREAM (stream);
    _im_assert (p);
    return im_memory_chunk_get_size (p->_chunk);
}

static void
__im_text_stream_initialize (IMPointer obj)
{
    IMTextStream *p = IM_TEXT_STREAM (obj);

    _im_assert (p);

    p->_chunk           = im_object_ref (im_memory_chunk_new (0));
    p->_read_pos        = 0;
    p->_write_pos       = 0;
    __clear_cur_info (p);
}

static void
__im_text_stream_finalize (IMPointer obj)
{
    IMTextStream *p = IM_TEXT_STREAM (obj);

    _im_assert (p);

    im_object_unref (p->_chunk);
}

static void
__im_text_stream_copy (IMPointer dest, IMConstPointer src)
{
    IMTextStream        *dp = IM_TEXT_STREAM (dest);
    const IMTextStream  *sp = IM_CONST_TEXT_STREAM (src);

    _im_assert (dp && sp);

    im_object_unref (dp->_chunk);
    dp->_chunk          = im_object_ref (im_object_clone (sp->_chunk));
    dp->_read_pos       = sp->_read_pos;
    dp->_write_pos      = sp->_write_pos;
    dp->_cur_type       = sp->_cur_type;
    dp->_cur_size       = sp->_cur_size;
    dp->_cur_type_len   = sp->_cur_type_len;
    dp->_cur_size_len   = sp->_cur_size_len;

    /* call parent copy */
    __im_text_stream_parent_class->copy (dest, src);
}

static IMBool
__im_text_stream_serialize (IMConstPointer obj, IMPointer stream)
{
    const IMTextStream  *op = IM_CONST_TEXT_STREAM (obj);
    IMStream            *sp = IM_STREAM (stream);

    _im_assert (op && sp);

    __free_unused_space (op);

    /* Call parent serialize method */
    return __im_text_stream_parent_class->serialize (obj, stream) &&
           im_stream_put_object (sp, op->_chunk);
}

static IMBool
__im_text_stream_deserialize (IMPointer obj, IMPointer stream)
{
    IMTextStream *op = IM_TEXT_STREAM (obj);
    IMStream     *sp = IM_STREAM (stream);

    _im_assert (op && sp);

    /* Call parent serialize method */
    if (!__im_text_stream_parent_class->deserialize (obj, stream))
        return FALSE;

    __im_text_stream_clear ((IMStream*)op);

    if (im_stream_get_data_type (sp) != IM_TYPE_MEMORY_CHUNK)
        return FALSE;

    if (im_stream_get_object (sp, op->_chunk)) {
        op->_write_pos = im_memory_chunk_get_size (op->_chunk);
        return TRUE;
    }
    return FALSE;
}

static void
__im_text_stream_class_init (IMPointer klass)
{
    IMObjectClass *cp  = (IMObjectClass *)klass;
    IMStreamClass *scp = (IMStreamClass *)klass;

    cp->copy                = __im_text_stream_copy;
    cp->serialize           = __im_text_stream_serialize;
    cp->deserialize         = __im_text_stream_deserialize;

    scp->put_void           = __im_text_stream_put_void;
    scp->put_char           = __im_text_stream_put_char;
    scp->put_uchar          = __im_text_stream_put_uchar;
    scp->put_bool           = __im_text_stream_put_bool;
    scp->put_int16          = __im_text_stream_put_int16;
    scp->put_uint16         = __im_text_stream_put_uint16;
    scp->put_int32          = __im_text_stream_put_int32;
    scp->put_uint32         = __im_text_stream_put_uint32;
    scp->put_int64          = __im_text_stream_put_int64;
    scp->put_uint64         = __im_text_stream_put_uint64;
    scp->put_double         = __im_text_stream_put_double;
    scp->put_c_string       = __im_text_stream_put_c_string;
    scp->put_byte_array     = __im_text_stream_put_byte_array;
    scp->put_bool_array     = __im_text_stream_put_bool_array;
    scp->put_int16_array    = __im_text_stream_put_int16_array;
    scp->put_uint16_array   = __im_text_stream_put_uint16_array;
    scp->put_int32_array    = __im_text_stream_put_int32_array;
    scp->put_uint32_array   = __im_text_stream_put_uint32_array;
    scp->put_int64_array    = __im_text_stream_put_int64_array;
    scp->put_uint64_array   = __im_text_stream_put_uint64_array;
    scp->put_double_array   = __im_text_stream_put_double_array;
    scp->put_object         = __im_text_stream_put_object;
    scp->clear              = __im_text_stream_clear;
    scp->get_data_type      = __im_text_stream_get_data_type;
    scp->get_data_size      = __im_text_stream_get_data_size;
    scp->get_void           = __im_text_stream_get_void;
    scp->get_char           = __im_text_stream_get_char;
    scp->get_uchar          = __im_text_stream_get_uchar;
    scp->get_bool           = __im_text_stream_get_bool;
    scp->get_int16          = __im_text_stream_get_int16;
    scp->get_uint16         = __im_text_stream_get_uint16;
    scp->get_int32          = __im_text_stream_get_int32;
    scp->get_uint32         = __im_text_stream_get_uint32;
    scp->get_int64          = __im_text_stream_get_int64;
    scp->get_uint64         = __im_text_stream_get_uint64;
    scp->get_double         = __im_text_stream_get_double;
    scp->get_c_string       = __im_text_stream_get_c_string;
    scp->get_byte_array     = __im_text_stream_get_byte_array;
    scp->get_bool_array     = __im_text_stream_get_bool_array;
    scp->get_int16_array    = __im_text_stream_get_int16_array;
    scp->get_uint16_array   = __im_text_stream_get_uint16_array;
    scp->get_int32_array    = __im_text_stream_get_int32_array;
    scp->get_uint32_array   = __im_text_stream_get_uint32_array;
    scp->get_int64_array    = __im_text_stream_get_int64_array;
    scp->get_uint64_array   = __im_text_stream_get_uint64_array;
    scp->get_double_array   = __im_text_stream_get_double_array;
    scp->get_object         = __im_text_stream_get_object;
    scp->skip_data          = __im_text_stream_skip_data;
    scp->rewind             = __im_text_stream_rewind;
    scp->read               = __im_text_stream_read;
    scp->write              = __im_text_stream_write;
    scp->size               = __im_text_stream_size;

    __im_text_stream_parent_class = im_object_class_get_parent (cp);
}

void
__im_type_register_text_stream ()
{
    static IMTypeInfo im_text_stream_type_info = 
    {
        sizeof (IMTextStreamClass), /**< class_size >**/
        __im_text_stream_class_init,/**< class_init >**/
        0,                          /**< class_finalize >**/

        sizeof (IMTextStream),      /**< instance_size >**/
        __im_text_stream_initialize,/**< instance_init >**/
        __im_text_stream_finalize,  /**< instance_finalize >**/
    };

    __im_type_register_builtin_class (IM_TYPE_STREAM,
                                      IM_TYPE_TEXT_STREAM,
                                      "TextStream",
                                      &im_text_stream_type_info,
                                      TRUE);
}

IMStream*
im_text_stream_new ()
{
    IMStream *s = (IMStream *) im_object_new (IM_TYPE_TEXT_STREAM);
    _im_assert (s);
    return s;
}

void
im_text_stream_set_memory_chunk (IMTextStream *stream, IMMemoryChunk *chunk)
{
    _im_return_if_fail (IM_IS_TEXT_STREAM (stream));
    _im_return_if_fail (IM_IS_MEMORY_CHUNK (chunk));

    im_object_unref (stream->_chunk);
    stream->_chunk = chunk;
    im_object_ref (stream->_chunk);

    stream->_write_pos      = im_memory_chunk_get_size (stream->_chunk);
    stream->_read_pos       = 0;
    __clear_cur_info (stream);
}

IMMemoryChunk*
im_text_stream_get_memory_chunk (const IMTextStream *stream)
{
    _im_return_val_if_fail (IM_IS_TEXT_STREAM (stream), 0);

    __free_unused_space (stream);

    return stream->_chunk;
}

IMMemoryChunk*
im_text_stream_dup_memory_chunk (const IMTextStream *stream)
{
    _im_return_val_if_fail (IM_IS_TEXT_STREAM (stream), 0);

    return im_memory_chunk_get_sub_chunk (stream->_chunk, 0, stream->_write_pos, FALSE);
}

/*
vi:ts=4:nowrap:ai:expandtab
*/
